/*
	cube.cpp

	Author: Chris Haag
	Description:

		Defines body of class that handles and draws
		cube motion. If you want the meat and potatoes
		of the collision code, go to cpsystem.cpp.

		Note: I use the terms cube and 'die' interchangeably.
*/

#include <stdlib.h>
#include "cube.h"

CCube::CCube(float x, float y, float z, float n) :
CPSystem()
{
	_VERTEX* v;
	int nIV = 0;

	m_Type = eCube;

	// Build physics
	m_vCenter.x = x;
	m_vCenter.y = y;
	m_vCenter.z = z;
	m_Mass = 1.0f / 100.0f;
	m_OneOverMass = 1.0f / m_Mass;

	n = n / 2;

	m_matInvI.ZeroMatrix();
	m_matInvI(0,0) = 3 / (m_Mass * (n*n+n*n));
	m_matInvI(1,1) = 3 / (m_Mass * (n*n+n*n));
	m_matInvI(2,2) = 3 / (m_Mass * (n*n+n*n));

	// Build vertex and face list
	m_vStatic = new _VERTEX[8];
	m_v = new _VERTEX[8];
	m_f = new _FACE[6];
	m_tex = new _TEXCOORD[8];

	v = &m_vStatic[m_nVertices++];
	v->x = x + n; v->y = y + n; v->z = z - n;
	v = &m_vStatic[m_nVertices++];
	v->x = x + n; v->y = y + n; v->z = z + n;
	v = &m_vStatic[m_nVertices++];
	v->x = x - n; v->y = y + n; v->z = z + n;
	v = &m_vStatic[m_nVertices++];
	v->x = x - n; v->y = y + n; v->z = z - n;

	v = &m_vStatic[m_nVertices++];
	v->x = x + n; v->y = y - n; v->z = z - n;
	v = &m_vStatic[m_nVertices++];
	v->x = x + n; v->y = y - n; v->z = z + n;
	v = &m_vStatic[m_nVertices++];
	v->x = x - n; v->y = y - n; v->z = z + n;
	v = &m_vStatic[m_nVertices++];
	v->x = x - n; v->y = y - n; v->z = z - n;

	
	AddCubeFace(nIV+3, nIV+2, nIV+1, nIV); // Top
	AddCubeFace(nIV+4, nIV+5, nIV+6, nIV+7); // Bottom

	AddCubeFace(nIV, nIV+1, nIV+5, nIV+4);
	AddCubeFace(nIV+2, nIV+3, nIV+7, nIV+6);

	AddCubeFace(nIV+1, nIV+2, nIV+6, nIV+5);
	AddCubeFace(nIV+3, nIV, nIV+4, nIV+7);
}

CCube::~CCube()
{
}

void CCube::AddCubeFace(unsigned short i1, unsigned short i2,
				 unsigned short i3, unsigned short i4)
{
	_FACE* f = &m_f[m_nFaces++];
	f->pIndices = new unsigned short[4];
	f->nIndices = 4;
	f->pIndices[0] = i1;
	f->pIndices[1] = i2;
	f->pIndices[2] = i3;
	f->pIndices[3] = i4;

	CalculateNormal(&m_vStatic[i1], &m_vStatic[i2], &m_vStatic[i3], f->vStaticNorm);
}

void CCube::Influence(CPSystem* p)
{
	if (p->GetType() != eCube /* If the influence p is not a cube */)
		return; /* Return because we don't want to influence non-cubes */

	int i = 0;
	while (CollisionDetect(this, p) && i < MAX_ALLOWABLE_COLLISIONS_PER_TRY)
		i++;
}

void CCube::Action(CPSystem* p)
{
	CPSystem::Action(p); // Reset change in physics state

	if (m_tLife > m_tLifeSpan || m_vCenter.y < -20)
	{
		Reset();
	}

	while (p)
	{
		if (p != this)
			p->Influence(this);

		p = p->m_pNext;
	}
}

void CCube::DrawSolid()
{
	_FACE* f = m_f;

	// Assume only six faces here. Also assume we
	// actually do care in what order the faces
	// of the dice are actually rendered.
	eTextures tex[] = { texOne, texSix, texTwo, texFive, texThree, texFour };

	glVertexPointer(3, GL_FLOAT, sizeof(_VERTEX), m_v);	
	glTexCoordPointer(2, GL_FLOAT, sizeof(_TEXCOORD), m_tex);

	for (int i=0; i < m_nFaces; i++, f++)
	{
		// This crazy code is used to ensure that
		// every side of the die has correct
		// texture coordinates. I didn't care how
		// they were oriented per face, so you will probably
		// see some inconsistencies with real dice.
		switch (i)
		{
		case 0:
			m_tex[0].x = 1; m_tex[0].y = 0;
			m_tex[1].x = 1; m_tex[1].y = 1;
			m_tex[2].x = 0; m_tex[2].y = 1;
			m_tex[3].x = 0; m_tex[3].y = 0;
			break;

		case 1:
			m_tex[4].x = 1; m_tex[4].y = 0;
			m_tex[5].x = 1; m_tex[5].y = 1;
			m_tex[6].x = 0; m_tex[6].y = 1;
			m_tex[7].x = 0; m_tex[7].y = 0;
			break;

		case 2:
			m_tex[0].x = 1; m_tex[0].y = 0;
			m_tex[1].x = 1; m_tex[1].y = 1;
			m_tex[5].x = 0; m_tex[5].y = 1;
			m_tex[4].x = 0; m_tex[4].y = 0;
			break;

		case 3:
			m_tex[2].x = 1; m_tex[2].y = 0;
			m_tex[3].x = 1; m_tex[3].y = 1;
			m_tex[7].x = 0; m_tex[7].y = 1;
			m_tex[6].x = 0; m_tex[6].y = 0;
			break;

		case 4:
			m_tex[1].x = 1; m_tex[1].y = 0;
			m_tex[2].x = 1; m_tex[2].y = 1;
			m_tex[6].x = 0; m_tex[6].y = 1;
			m_tex[5].x = 0; m_tex[5].y = 0;
			break;

		case 5:
			m_tex[3].x = 1; m_tex[3].y = 0;
			m_tex[0].x = 1; m_tex[0].y = 1;
			m_tex[4].x = 0; m_tex[4].y = 1;
			m_tex[7].x = 0; m_tex[7].y = 0;
			break;

		}

		glBindTexture ( GL_TEXTURE_2D, g_texture_id[tex[i]] );
		glNormal3f(f->vNorm.a, f->vNorm.b, f->vNorm.c);
		glDrawElements(GL_TRIANGLE_FAN, f->nIndices, GL_UNSIGNED_SHORT, f->pIndices);
	}
}

void CCube::Draw()
{
	float fAmb[4] = { 0.5f,0.5f,0.5f,1 };
	float fDiff[4] = { 1,1,1,1 };

	// If the die is about to disappear (and then reappear
	// at the top), then this code will make it fade away.
	if (m_tLife > m_tLifeSpan * 0.9f)
	{
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		fAmb[3] = fDiff[3] = (m_tLifeSpan - m_tLife) * 0.9f;
	}

	glMaterialfv(GL_FRONT, GL_AMBIENT, fAmb);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, fDiff);

	// Ok, actually draw the faces here.
	DrawSolid();

	if (m_tLife > m_tLifeSpan * 0.9f)
	{
		glDisable(GL_BLEND);
	}
}

void CCube::DrawShadow(GLfloat shadowMat[4][4])
{
	if (m_vCenter.y < 0) return;

	float fAmb[4] = { 0.4f,0.4f,0.4f,1 };
	float fDiff[4] = { 1,1,1,1 };

	glMaterialfv(GL_FRONT, GL_AMBIENT, fAmb);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, fDiff);

	// Make shadow intensity a function of distance from ground
	float fIntensity = 0.5f - (m_vCenter.y / 100.0f);
	if (fIntensity < 0) fIntensity = 0;
	if (fIntensity > 1) fIntensity = 1;

	glEnable(GL_TEXTURE_2D);
	glColor4f(fIntensity, fIntensity, fIntensity, fIntensity);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	DrawSolid();
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glDisable(GL_TEXTURE_2D);
}


// This is called at the start of the program, and when
// the lifespan of the cube expires.
void CCube::Reset()
{
	_VERTEX vNew = { 15 - rand() % 30, 250 + rand() % 150, 10 - rand() % 20 };
	_VERTEX* v = m_v;

	float dx = vNew.x - m_vCenter.x;
	float dy = vNew.y - m_vCenter.y;
	float dz = vNew.z - m_vCenter.z;

	// Reset the cube's center of mass
	m_vCenter.x = vNew.x;
	m_vCenter.y = vNew.y;
	m_vCenter.z = vNew.z;

	// Offset the cube's vertices to their
	// new positions.
	for (int i=0; i < m_nVertices; i++, v++)
	{
		v->x += dx;
		v->y += dy;
		v->z += dz;
	}

	// Pick a random velocity (in the direction of
	// the stairs' decent).
	m_vVel.z = (float)(-10 - rand() % 10);

	// Reset the life counter and choose a random lifespan.
	m_tLife = 0;
	m_tLifeSpan = 30 + rand() % 60;
}
